/****************************************************************************
 * Copyright (c) 2004 Composent, Inc. and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *    Composent, Inc. - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/
package org.eclipse.ecf.internal.provider.xmpp.filetransfer;

import java.io.File;
import java.io.FileInputStream;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.identity.IDCreateException;
import org.eclipse.ecf.core.identity.IDFactory;
import org.eclipse.ecf.filetransfer.FileTransferJob;
import org.eclipse.ecf.filetransfer.IFileTransferInfo;
import org.eclipse.ecf.filetransfer.IFileTransferListener;
import org.eclipse.ecf.filetransfer.IOutgoingFileTransfer;
import org.eclipse.ecf.filetransfer.UserCancelledException;
import org.eclipse.ecf.filetransfer.events.IFileTransferEvent;
import org.eclipse.ecf.filetransfer.events.IOutgoingFileTransferResponseEvent;
import org.eclipse.ecf.filetransfer.events.IOutgoingFileTransferSendDoneEvent;
import org.eclipse.ecf.internal.provider.xmpp.XmppPlugin;
import org.eclipse.ecf.provider.xmpp.identity.XMPPID;
import org.eclipse.osgi.util.NLS;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.filetransfer.FileTransfer;
import org.jivesoftware.smackx.filetransfer.FileTransfer.Status;
import org.jivesoftware.smackx.filetransfer.FileTransferManager;
import org.jivesoftware.smackx.filetransfer.OutgoingFileTransfer;

public class XMPPOutgoingFileTransfer implements IOutgoingFileTransfer {

	private final ID sessionID;
	private final XMPPID remoteTarget;
	private final IFileTransferListener listener;

	private File localFile;

	private long fileSize;

	private final OutgoingFileTransfer outgoingFileTransfer;

	private Status status;

	private Exception exception;

	private int originalOutputRequestTimeout = -1;

	private boolean localCancelled = false;

	public XMPPOutgoingFileTransfer(FileTransferManager manager,
			XMPPID remoteTarget, IFileTransferInfo fileTransferInfo,
			IFileTransferListener listener, int outgoingRequestTimeout) {
		this.remoteTarget = remoteTarget;
		this.listener = listener;
		this.sessionID = createSessionID();
		final String fullyQualifiedName = remoteTarget.getFQName();
		// Set request timeout if we have a new value
		if (outgoingRequestTimeout != -1) {
			originalOutputRequestTimeout = OutgoingFileTransfer
					.getResponseTimeout();
			OutgoingFileTransfer.setResponseTimeout(outgoingRequestTimeout);
		}
		outgoingFileTransfer = manager
				.createOutgoingFileTransfer(fullyQualifiedName);
	}

	private ID createSessionID() {
		try {
			return IDFactory.getDefault().createGUID();
		} catch (final IDCreateException e) {
			throw new NullPointerException(
					"cannot create id for XMPPOutgoingFileTransfer"); //$NON-NLS-1$
		}
	}

	public synchronized ID getRemoteTargetID() {
		return remoteTarget;
	}

	public ID getID() {
		return sessionID;
	}

	private void fireTransferListenerEvent(IFileTransferEvent event) {
		listener.handleTransferEvent(event);
	}

	private void setStatus(Status s) {
		this.status = s;
	}

	private void setException(Exception e) {
		this.exception = e;
	}

	private Status getStatus() {
		return this.status;
	}

	private void setErrorStatus(Exception e) {
		setStatus(FileTransfer.Status.error);
		setException(e);
	}

	public synchronized void startSend(File localFile, String description)
			throws XMPPException {
		this.localFile = localFile;
		this.fileSize = localFile.length();
		setStatus(Status.initial);

		outgoingFileTransfer.sendFile(localFile, description);

		final Thread transferThread = new Thread(new Runnable() {
			public void run() {
				setStatus(outgoingFileTransfer.getStatus());
				boolean negotiation = true;
				try {
					while (negotiation && !localCancelled) {
						// check the state of the progress
						try {
							Thread.sleep(300);
						} catch (final InterruptedException e) {
							setErrorStatus(e);
							return;
						}
						final Status s = outgoingFileTransfer.getStatus();
						setStatus(s);
						final boolean negotiated = getStatus().equals(
								Status.negotiated);
						if (s.equals(Status.negotiated)
								|| s.equals(Status.cancelled)
								|| s.equals(Status.complete)
								|| s.equals(Status.error)
								|| s.equals(Status.refused)) {
							fireTransferListenerEvent(new IOutgoingFileTransferResponseEvent() {
								public boolean requestAccepted() {
									return negotiated;
								}

								public IOutgoingFileTransfer getSource() {
									return XMPPOutgoingFileTransfer.this;
								}

								public String toString() {
									final StringBuffer buf = new StringBuffer(
											"OutgoingFileTransferResponseEvent["); //$NON-NLS-1$
									buf.append("requestAccepted=").append(requestAccepted()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
									return buf.toString();
								}

								public void setFileTransferJob(
										FileTransferJob job) {
									// does nothing with this implementation
								}
							});
							// And negotiation is over
							negotiation = false;
						}
					}

					if (localCancelled) {
						setErrorStatus(new UserCancelledException(
								"Transfer cancelled by sender")); //$NON-NLS-1$
						return;
					}

					outgoingFileTransfer.sendStream(new FileInputStream(
							XMPPOutgoingFileTransfer.this.localFile),
							XMPPOutgoingFileTransfer.this.localFile.getName(),
							fileSize, "Ein File");
					setStatus(Status.complete);
				} catch (final Exception e) {
					setStatus(FileTransfer.Status.error);
					setException(e);
				} finally {
					// Reset request timeout
					if (originalOutputRequestTimeout != -1) {
						OutgoingFileTransfer
								.setResponseTimeout(originalOutputRequestTimeout);
					}
					// Then notify that the sending is done
					fireTransferListenerEvent(new IOutgoingFileTransferSendDoneEvent() {
						public IOutgoingFileTransfer getSource() {
							return XMPPOutgoingFileTransfer.this;
						}

						public String toString() {
							final StringBuffer buf = new StringBuffer(
									"IOutgoingFileTransferSendDoneEvent["); //$NON-NLS-1$
							buf.append("isDone=" + getSource().isDone()); //$NON-NLS-1$
							buf.append(";bytesSent=").append(getSource().getBytesSent()); //$NON-NLS-1$
							buf.append(";exception=").append(getException()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
							return buf.toString();
						}
					});
				}
			}
		}, NLS.bind("XMPP send {0}", remoteTarget.toExternalForm())); //$NON-NLS-1$

		transferThread.start();
	}

	public synchronized void cancel() {
		localCancelled = true;
	}

	public synchronized File getLocalFile() {
		return localFile;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	public Object getAdapter(Class adapter) {
		if (adapter == null)
			return null;
		if (adapter.isInstance(this))
			return this;
		final IAdapterManager adapterManager = XmppPlugin.getDefault()
				.getAdapterManager();
		return (adapterManager == null) ? null : adapterManager.loadAdapter(
				this, adapter.getName());
	}

	public long getBytesSent() {
		return outgoingFileTransfer.getBytesSent();
	}

	public Exception getException() {
		return exception;
	}

	public double getPercentComplete() {
		return (fileSize <= 0) ? 1.0 : (((double) outgoingFileTransfer
				.getAmountWritten()) / ((double) fileSize));
	}

	public boolean isDone() {
		return status == Status.cancelled || status == Status.error
				|| status == Status.complete;
	}

	public ID getSessionID() {
		return sessionID;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.filetransfer.IFileTransfer#getFileLength()
	 */
	public long getFileLength() {
		return fileSize;
	}

}
